package com.hframe.xml;

import com.hframe.data.Base;
import com.hframe.data.DRef;
import com.hframe.type.*;
import com.hframe.typefilter.DateToString;
import com.hframe.typefilter.FilterRef;
import org.apache.log4j.Logger;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Map;

/**
 * 对像转换成XML
 * Created by heqian on 17-9-22.
 */
public class DataToXml implements FilterRef<Object, Object> {
    private static Logger log = Logger.getLogger(DataToXml.class);

    @Override
    public Element filter(Object object) throws Exception {
        if (object instanceof DRef<?>) {
            return convert((DRef<?>) object);

        } else if (object instanceof ArrayList<?>) {
            return convert((ArrayList<Base>) object, "item", DocumentHelper.createElement("root"));

        } else if (object instanceof TData<?>) {
            String outKey[] = null;
            XmlFiletr filetr = ((TData<?>) object).getfilter(XmlFiletr.class);
            if (null != filetr)
                outKey = filetr.getKeys();
            return convert(((TData<?>) object).get(), DocumentHelper.createElement("root"),
                    null == outKey ? new String[0] : outKey);

        } else if (object instanceof TArray<?>) {
            String name = null;
            String outKey[] = null;
            XmlFiletr filetr = ((TArray<?>) object).getfilter(XmlFiletr.class);
            if (null != filetr) {
                name = filetr.getName();
                outKey = filetr.getKeys();
            }
            return convert((ArrayList<Base>) ((TArray<?>) object).get(), null == name ? "item" : name,
                    DocumentHelper.createElement("root"), null == outKey ? new String[0] : outKey);
        }

        return null;
    }

    public static Element convert(DRef<?> object, String... key) {
        return convert(object, DocumentHelper.createElement("root"), key);
    }

    public static Element convert(DRef<?> data, Element root, String... key) {
        Class<?> cla = data.getClass();
        while (null != cla && cla != DRef.class) {
            for (Field field : cla.getDeclaredFields()) {
                field.setAccessible(true);
                try {
                    if (!isExtendsClass(field.getType(), T.class))
                        continue;

                    String fieldName = field.getName();
                    Object object = field.get(data);

                    if (object == null) {
                        if (isOut(fieldName, key))
                            root.addElement(fieldName);
                    } else {
                        T<?> typeObj = (T<?>) object;
                        XmlFiletr filetr = typeObj.getfilter(XmlFiletr.class);
                        if (0 < key.length) {
                            if (!isOut(fieldName, key))
                                continue;
                        } else {
                            if (null != filetr && (filetr.getReadWait() == XmlFiletr.ReadWait.OnlyWait || filetr.getReadWait() == XmlFiletr.ReadWait.NotReadWait))
                                continue;
                        }
                        String name = null;
                        Object val;
                        String[] subkey = null;
                        if (null != filetr) {
                            name = filetr.getName();
                            val = filetr.get();
                            subkey = filetr.getKeys();
                        } else
                            val = typeObj.get();

                        if (null == name)
                            name = fieldName;

                        addElement(fieldName, root, val, filetr, null == subkey ? new String[0] : subkey);

                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
            cla = cla.getSuperclass();
        }

        return root;
    }

    private static void addElement(String name, Element root, Object val, XmlFiletr filetr, String... outKey) {
        boolean idData = null != filetr && filetr.isData();
        Element element;
        if (null == val) {
            element = root.addElement(name);
            addFiletr(element, filetr);
            if (idData)
                element.addCDATA(null);
            else
                element.addText(null);

        } else if (val instanceof Node) {
            element = root.addElement(name);
            addFiletr(element, filetr);
            element.add((Node) val);

        } else if (val instanceof java.util.Date) {
            element = root.addElement(name);
            addFiletr(element, filetr);
            element.addText(DateToString.format((java.util.Date) val, DateToString.DATE_TIME_LINE));

        } else if (val instanceof String) {
            element = root.addElement(name);
            addFiletr(element, filetr);
            if (idData)
                element.addCDATA((String) val);
            else
                element.addText((String) val);

        } else if (val instanceof Enum) {
            element = root.addElement(name);
            addFiletr(element, filetr);
            if (idData)
                element.addCDATA("" + ((Enum) val).ordinal());
            else
                element.addText("" + ((Enum) val).ordinal());

        } else if (val instanceof DRef<?>) {
            element = root.addElement(name);
            addFiletr(element, filetr);
            convert((DRef<?>) val, element, outKey);

        } else if (val instanceof ArrayList<?>) {
            convert((ArrayList<Base>) val, name, root, outKey);

        } else {
            element = root.addElement(name);
            addFiletr(element, filetr);
            if (idData)
                element.addCDATA(val.toString());
            else
                element.addText(val.toString());
        }
    }

    public static Element convert(TArray<?> array, String... outKey) {
        String name = "item";
        XmlFiletr filetr = array.getfilter(XmlFiletr.class);
        if (null != filetr && null != filetr.getName())
            name = filetr.getName();

        return convert((ArrayList<Base>) array.get(), name, DocumentHelper.createElement("root"), outKey);
    }

    private static Element convert(ArrayList<Base> array, String name, Element root, String... outKey) {
        for (Base item : array) {
            Element element = root.addElement(name);

            if (null == item || (item instanceof T && ((T) item).isEmpty())) {
                element.addText(null);

            } else if (item instanceof TDate) {
                element.addText(DateToString.format(((TDate) item).get(), DateToString.DATE_TIME_LINE));

            } else if (item instanceof TEnum) {
                element.addText("" + ((Enum) ((T<?>) item).get()).ordinal());

            } else if (item instanceof DRef<?>) {
                convert((DRef<?>) item, element, outKey);

            } else if (item instanceof TArray<?>) {
                String subName = "item";
                XmlFiletr filetr = ((TArray<?>) item).getfilter(XmlFiletr.class);
                if (null != filetr && null != filetr.getName())
                    name = filetr.getName();
                convert((ArrayList<Base>) ((TArray<?>) item).get(), subName, element, outKey);

            } else if (item instanceof TString) {
                element.addText(((TString) item).get());

            } else {
                element.addText(item.toString());
            }
        }
        return root;
    }

    private static void addFiletr(Element element, XmlFiletr filetr) {
        if (null != filetr) {
            for (Map.Entry<String, String> item : filetr.getAttributes().entrySet()) {
                element.addAttribute(item.getKey(), item.getValue());
            }

            if (null != filetr.getComment())
                element.addComment(filetr.getComment());

            if (null != filetr.getNamespace() && null != filetr.getValuespace())
                element.addNamespace(filetr.getNamespace(), filetr.getValuespace());
        }
    }

    private static boolean isOut(String name, String[] keys) {
        for (String key : keys) {
            if (key.toLowerCase().equals(name.toLowerCase()))
                return true;
        }
        return false;
    }

    protected static boolean isExtendsClass(Class<?> src, Class<?> dis) {
        while (src != null) {
            if (src == dis)
                return true;
            src = src.getSuperclass();
        }
        return false;
    }
}
